/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.static_mocking;

import picounit.InjectorAPI;
import picounit.features.base.Caller;
import picounit.features.base.exception.ClassNotFound;
import picounit.features.classloader.PicoUnitClassLoader;
import picounit.features.generic_return_type.Cast;
import picounit.features.generic_return_type.CheckCastRegistry;
import picounit.features.mocking.TypeFactory;

import java.util.HashSet;
import java.util.Set;

public class InjectorImpl implements InjectorAPI {
	private static InjectorImpl instance;

	public static InjectorAPI instance() {
		synchronized (InjectorImpl.class) {
			if (instance == null) {
				instance = new InjectorImpl();
			}

			return instance;
		}
	}
	
	static void configure(PicoUnitClassLoader picoUnitClassLoader,
		CheckCastRegistry checkCastRegistry, TypeFactory typeFactory) {

		instance();

		instance.setClassLoader(picoUnitClassLoader);
		instance.setCheckCastRegistry(checkCastRegistry);
		instance.setTypeFactory(typeFactory);
	}

	// Instead of obtaining these during feature loading time (which occurs multiple times if PicoUnit is used twice)
	// obtain the values appropriate to the caller
	// Problem: I only know the caller via the stack trace, I need record all the class loaders and ask each which
	// has loaded the caller ?

	private CheckCastRegistry checkCastRegistry;
	private TypeFactory typeFactory;
	
	private final Set<PicoUnitClassLoader> classLoaders = new HashSet<PicoUnitClassLoader>();
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// InjectorAPI methods
	///////////////////////////////////////////////////////////////////////////////////////////////

    public <T> T injectCollaborator() {
    	return inject(null);
    }

    public <T> T injectCollaborator(Class<?> callingClass) {
    	return inject(callingClass);
    }

    public <T> T injectFixture() {
        return inject(null);
    }
    
    public <T> T injectFixture(Class<?> callingClass) {
    	return inject(callingClass);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation methods
	///////////////////////////////////////////////////////////////////////////////////////////////
    
	private void setCheckCastRegistry(CheckCastRegistry checkCastRegistry) {
		if (this.checkCastRegistry == null) {
			this.checkCastRegistry = checkCastRegistry;
		}
	}
	
	private void setTypeFactory(TypeFactory typeFactory) {
		if (this.typeFactory == null) {
			this.typeFactory = typeFactory;
		}
	}

	private void setClassLoader(PicoUnitClassLoader classLoader) {
		classLoaders.add(classLoader);
	}

    @SuppressWarnings("unchecked")
	private <T> T inject(Class<?> callingClass) {
    	Caller caller = new Caller(4);

    	Cast cast = checkCastRegistry.getCast(caller);

		Class<?> castedTypeClass = 
			cast.loadUsing(classLoader(callingClass, caller.className()));

		return (T) typeFactory.create(castedTypeClass);
	}

	private ClassLoader classLoader(Class<?> callingClass, String className) {
		if (callingClass != null) {
			return callingClass.getClassLoader();
		}
		
		for (PicoUnitClassLoader classLoader : classLoaders) {
			if (classLoader.loadedClass(className)) {
				System.out.println(String.format("Using classLoader: %s, for %s", classLoader, className));

				return classLoader;
			}
		}

		throw new ClassNotFound("No PicoUnitClassLoader has loaded: " + className);
	}

}
